'use strict';

/*-------------------------------------------------------------
       _                         
 _   _(_) __ _ _ __ _____      __
| | | | |/ _` | '__/ _ \ \ /\ / /
| |_| | | (_| | | | (_) \ V  V / 
\__ , |_|\__, |_|  \___/ \_/\_/  
 |___/   |___/ (C)2023 shhoo.cn

 1、查询可入网设备
 2、设置入网设备名称及路数
-------------------------------------------------------------*/
/*-----------时间--------*/
import moment from 'moment';
moment.locale('zh-cn');
import * as ModbusRTURequest from "../public/modbus/rtu-request"
import { ModbusRTUResponse } from "../public/modbus/rtu-response"
const events = require("events");
import YAML from 'yamljs';
const Settings = YAML.load('settings.yaml');
const TCP_COM_CTRL = Settings.TCP_COM_CTRL;
const EventEmitter = events.EventEmitter || events;
const JS_NAME = 'tcpComCtrl.ts';
const LORA_BROADCAST_ADDRESS = "00000071";//lora广播地址
import { Tcp_Client_Extension } from "../lib/tcpClient";
/**
 * 
 * @param channel 
 * @param message 
 * @example
 * let test = { "isRight": true, "msg": "ModBus Cmd", "host": "36240200", "lsnr": 4.2, "rssi": -70, "datr": "SF9BW125", "recvData": "01030A000100000000000100001AFA" }
 * _onReceive("SERIAL_CMD_ACK", JSON.stringify(test))
 */
function _onReceive(host: string, data: string) {
      const modbus = this;
      console.log(`\x1b[0;32mRcvData【${host}】<-----${Buffer.from(data, "hex").toString('hex').toUpperCase()}\x1b[0;37m`)
      try {
            let mbus = ModbusRTUResponse.fromBuffer(Buffer.from(data, "hex"));
            // console.log(mbus)
            if (mbus != null && mbus._address == 1) {//站号为1
                  switch (mbus._body._fc) {
                        case 1://readCoils(DO)

                              break;
                        case 2://readDiscreteInputs(DI)

                              break;
                        case 3://readHoldingRegisters(QueryApplayDevice)
                              let frameId = mbus._body._values[0];
                              if (frameId == 0) {//申号入网
                                    //                                         0             2             4            6            8            10             12        14 
                                    // 站号(1byte) 功能码(1byte) 数据长度(1byte)  帧标识    DO数量(2byte) DI数量(2byte) AI数量(2byte) hwVer(2byte) swVer(2byte) SNR(2byte) RSSI(2byte) CRC16(2byte)
                                    // 01         03           10              0000           000C          0008         0008          0032         000A         0AE0       22F0        2271
                                    // 01         03           10              0000           001000080000000B000C01E0BAF02946
                                    console.log(`设备入网`)
                                    let jugeSnr = mbus._body._valuesAsBuffer.slice(13, 14).toString('hex').toUpperCase();
                                    let lsnr = mbus._body._valuesAsBuffer.slice(12, 13)[0];
                                    lsnr = jugeSnr == `F0` ? -lsnr : lsnr;
                                    let jugeRssi = mbus._body._valuesAsBuffer.slice(15, 16).toString('hex').toUpperCase();
                                    let rssi = mbus._body._valuesAsBuffer.slice(14, 15)[0];
                                    rssi = jugeRssi == `F0` ? -rssi : rssi;
                                    let swVer = `V` + (`${mbus._body._valuesAsBuffer.slice(8, 9)[0]}` + mbus._body._valuesAsBuffer.slice(9, 10).toString('hex').toUpperCase()).split("").join(".");
                                    let hwVer = `V` + (`${mbus._body._valuesAsBuffer.slice(10, 11)[0]}` + mbus._body._valuesAsBuffer.slice(11, 12).toString('hex').toUpperCase()).split("").join(".");
                                    const transaction_FC3 = modbus._transactions[`QueryApplayDevice_${host}`];
                                    console.log(`${new Date().toLocaleString()}`, `\x1b[30;33m【设备入网】地址为：${host},DONum:${mbus._body._values[1]},DINum:${mbus._body._values[2]},AINum:${mbus._body._values[3]},lsnr:${lsnr},rssi:${rssi},swVer:${swVer},hwVer:${hwVer}\x1b[30;337m`);
                                    if (transaction_FC3.next) {
                                          transaction_FC3.isCtrlOk = true;
                                          transaction_FC3.next(null, {
                                                host: transaction_FC3.host,
                                                DONum: mbus._body._values[1],
                                                DINum: mbus._body._values[2],
                                                AINum: mbus._body._values[3],
                                                glsnr: 0,
                                                grssi: 0,
                                                lsnr: lsnr,
                                                rssi: rssi,
                                                swVer: swVer,
                                                hwVer: hwVer
                                          })
                                          transaction_FC3.next = null;
                                    }
                              } else if (frameId == 1) {//writeRegisters(MULT_TIMER_DOS)
                                    // 【loRaComCtrl.ts/<WriteTimerCoils>(host:E240100B)】--->>>  01 10 0000 0004 08 0000 00000001006E6656
                                    //01 03 02 00010000000000020000000A00000006000000080000000400000001000800030008000B000800070008000900080005000829FA
                                    // console.log(mbus)
                                    console.log(`\x1b[30;31m【${host}】收到定时DO控制回执，起始地址：${mbus._body._values[1]},控制数量：${(mbus._body._values.length - 1) / 2}，[通道，时长]=[${mbus._body._values.slice(1)}]\x1b[30;37m`)
                                    const transaction_FC16_0 = modbus._transactions[`WriteTimerCoils_${host}_${mbus._body._values[1]}_${(mbus._body._values.length - 1) / 2}`];
                                    // console.log(transaction_FC16_0)
                                    if (transaction_FC16_0.next) {
                                          transaction_FC16_0.isCtrlOk = true;
                                          let openSec: number[] = [];
                                          for (let i = 1; i < mbus._body._values.length; i++) {
                                                if (i % 2 == 0) {
                                                      const element = mbus._body._values[i];
                                                      openSec.push(element);
                                                }
                                          }
                                          console.log(openSec)
                                          transaction_FC16_0.next(null, {
                                                host: transaction_FC16_0.host,
                                                start: mbus._body._values[1],
                                                quantity: mbus._body._values.length - 1,
                                                adressArr: transaction_FC16_0.adressArr,
                                                openTimeSecArr: openSec,
                                          })
                                          transaction_FC16_0.next = null;
                                    }

                              }


                              break;
                        case 4://readInputRegisters(AI)

                              break;
                        case 5://writeCoil(DO)
                              const transaction_FC5 = modbus._transactions[`WriteCoil_${host}_${mbus._body._address}_${mbus._body._value}`];
                              // console.log(mbus,transaction_FC5)
                              if (transaction_FC5.next) {
                                    // console.log(transaction_FC5.address, mbus._body._address && transaction_FC5.value, mbus._body._value)
                                    if (transaction_FC5.address == mbus._body._address && transaction_FC5.value == mbus._body._value) {
                                          transaction_FC5.isCtrlOk = true;
                                          transaction_FC5.next(null, {
                                                host: transaction_FC5.host,
                                                address: transaction_FC5.address,
                                                value: transaction_FC5.value
                                          })
                                          transaction_FC5.next = null;
                                    }
                              }

                              break;
                        case 6://writeRegister(TIMER_DO)
                              const transaction_FC6 = modbus._transactions[`WriteTimerCoil_${host}_${mbus._body._address}`];
                              // console.log(transaction_FC6)
                              if (transaction_FC6.next) {
                                    // console.log(transaction_FC6.address, mbus._body._address && transaction_FC6.value, mbus._body._value)
                                    if (transaction_FC6.address == mbus._body._address && transaction_FC6.value == mbus._body._value) {
                                          transaction_FC6.isCtrlOk = true;
                                          transaction_FC6.next(null, {
                                                host: transaction_FC6.host,
                                                address: transaction_FC6.address,
                                                value: transaction_FC6.value
                                          })
                                          transaction_FC6.next = null;
                                    }
                              }
                              break;
                        case 15://writeCoils(DO)
                              const transaction_FC15 = modbus._transactions[`WriteCoils_${host}`];
                              if (transaction_FC15.next) {
                                    // console.log(transaction_FC15.start, mbus._body._start && transaction_FC15.quantity, mbus._body._quantity)
                                    if (transaction_FC15.start == mbus._body._start && transaction_FC15.quantity == mbus._body._quantity) {
                                          transaction_FC15.isCtrlOk = true;
                                          transaction_FC15.next(null, {
                                                host: transaction_FC15.host,
                                                start: mbus._body._start,
                                                quantity: mbus._body._quantity,
                                                values: transaction_FC15.values,
                                          })
                                          transaction_FC15.next = null;
                                    }
                              }
                              break;
                        case 16:
                              // console.log(mbus)
                              const transaction_FC16_1 = modbus._transactions[`WriteDelayCoils_${host}`];
                              console.log(mbus)
                              if (transaction_FC16_1.next) {
                                    console.log(transaction_FC16_1.start, mbus._body._start, transaction_FC16_1.quantity, mbus._body._quantity)
                                    if (transaction_FC16_1.start == mbus._body._start || 0xffff == mbus._body._start) {
                                          transaction_FC16_1.isCtrlOk = true;
                                          transaction_FC16_1.next(null, {
                                                coils16Sta: mbus._body._start,
                                                delay: mbus._body._quantity,
                                                coilsStatusArr: transaction_FC16_1.coilsStatusArr
                                          })
                                          transaction_FC16_1.next = null;
                                    }
                              }
                              break;

                        default:
                              break;
                  }

            } else {
                  console.log(`数据格式错误`)
            }
      } catch (error) {

      }
}


/**
 * 
 * @param funcName 函数名
 * @param cmdPacket 
 * @param transactionId 
 * @param retryInterval 
 * @param retryNumber 
 * @param isBackOff 
 */
function _writeCmdToTcpClient(funcName: string, cmdPacket: string, transactionId: string, retryInterval: number, retryNumber: number, isBackOff: boolean) {
      const transaction = this._transactions[transactionId];
      // console.log(transaction)
      if (transaction) {
            if (transaction.interval != null) {
                  clearInterval(transaction.interval);
                  transaction.interval = null;
            }
            let tcpClient = this.client.find((clientInfo: ClientInfo) => {
                  return clientInfo.host == transaction.host;
            })
            // console.log(tcpClient)
            if (tcpClient) {
                  tcpClient.client.tcpClientSendMsg(Buffer.from(cmdPacket, 'hex'));
                  console.log(`${new Date().toLocaleString()}`, `\x1b[30;32m【${`${JS_NAME}/<${funcName}>`}(host:${transaction.host})】--->>>${cmdPacket}\x1b[30;37m`);
                  transaction.interval = setInterval(() => {
                        let perTimes = Math.round(retryInterval / 100);
                        let reCnt = isBackOff ? Math.round(perTimes * (1 + transaction.retryCount / 2)) : perTimes;
                        if (transaction.cnt >= reCnt) {
                              if (transaction.isCtrlOk == false && transaction.host != LORA_BROADCAST_ADDRESS) {
                                    // console.log(reCnt)
                                    if (transaction.retryCount < retryNumber) {
                                          console.log(`\x1b[30;33m${`${JS_NAME}/<${funcName}>`}】设备(host:${transaction.host})控制尝试重发第${transaction.retryCount + 1}次指令(${cmdPacket})\x1b[30;37m`)
                                          tcpClient.client.tcpClientSendMsg(Buffer.from(cmdPacket, 'hex'));
                                          transaction.retryCount++;
                                    } else {
                                          clearInterval(transaction.interval);
                                          tcpClient.client.isConnected = false;
                                          transaction.interval = null;
                                          console.log(`\x1b[30;31m【${`${JS_NAME}/<${funcName}>`}】设备(host:${transaction.host})控制尝试重发${retryNumber}次指令(${cmdPacket})控制失败\x1b[30;37m`)
                                          transaction.next(`设备(host:${transaction.host})控制尝试重发${retryNumber}次指令控制失败`, null);
                                    }
                                    transaction.cnt = 0;
                              } else {
                                    clearInterval(transaction.interval);
                              }
                        }
                        transaction.cnt++;
                  }, 20)
            }

      } else {
            console.log(transaction)
      }
}
interface ClientInfo {
      host: string,
      port: number,
      client: Tcp_Client_Extension,
};
class TcpComCtrlClass extends EventEmitter {
      client: ClientInfo[]
      /**
       * Class making tcpComCtrl calls fun and easy.
       *
       */
      constructor() {
            super();
            this._transactions = {};
            this.client = [];
            this._onReceive = _onReceive.bind(this);
      }

      /**
       * Open the redisGateway and register tcpComCtrl parsers
       *
       *      of failure.
       */
      open() {
            const modbus = this;
            const enable = TCP_COM_CTRL.enable;
            const host = TCP_COM_CTRL.host;
            const port = TCP_COM_CTRL.port;
            for (let i = 0; i < host.length; i++) {
                  if (enable[i] == true) {
                        // console.log(host[i], port[i])
                        let tcpClient = new Tcp_Client_Extension(host[i], port[i]);
                        tcpClient.on("data", function (data: string) {
                              console.log(`\x1b[0;32mRcvData【${host[i]}】<-----${Buffer.from(data, "hex").toString('hex').toUpperCase()}\x1b[0;37m`)
                              modbus._onReceive(host[i], data);
                              tcpClient.setIsRcvOk()
                        })
                        let clientInfo: ClientInfo = {
                              host: host[i],
                              port: port[i],
                              client: tcpClient
                        }
                        // console.log(clientInfo)
                        if (clientInfo) {
                              modbus.client.push(clientInfo)
                        }
                  }
            }

      }


      /**
       * 设备入网查询
       * @param host Tcp地址
       * @param stationNumber 站号
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 回调
       * @example
       * tcpComCtrl.QueryApplayDevice("00000071",1, 1500, 2, false, function (err: string | null, data: object | null) {
           console.log(`==============TEST QueryApplayDevice==============`)
           console.log(err, data)
         })
       */
      QueryApplayDevice(host: string, stationNumber: number, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            let ackFrameBuf = ModbusRTURequest.ReadHoldingRegisters(stationNumber, 0, 8);
            let cmdPacket = ackFrameBuf;
            // set state variables
            let transactions_id = `QueryApplayDevice_${host}`;
            this._transactions[transactions_id] = {
                  host: host,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToTcpClient.call(this, `QueryApplayDevice_${host}`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }


      /**
       * 写单线圈（控制开关&风机等）
       * @param host Tcp地址
       * @param stationNumber 站号
       * @param address 写线圈地址
       * @param value 写线圈状态值
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 回调
       * @example
      tcpComCtrl.WriteCoil(host,1, 0, true, 2000, 3, false, function (err: string | null, data: object) {
            console.log(err, data)
      })
       */
      WriteCoil(host: string, stationNumber: number, address: number, value: boolean, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            let ackFrameBuf = ModbusRTURequest.WriteSingleCoil(stationNumber, address, value);
            let cmdPacket = ackFrameBuf;
            let transactions_id = `WriteCoil_${host}_${address}_${value}`;
            this._transactions[transactions_id] = {
                  host: host,
                  address: address,
                  value: value,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToTcpClient.call(this, `WriteCoil`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }
      /**
       * 写多个连续DO状态
       * @param host 地址
       * @param stationNumber 站号 
       * @param start 起始DO地址
       * @param values 设定的多个连续DO的状态值
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      tcpComCtrl.WriteCoils(host, 1, 0, [false, false, false, false, false, false, false, false, false, false, false, false], 2000, 3, true, function (err: string | null, data: WriteCoilInfo) {
           console.log(err, data)
      })
       */
      WriteCoils(host: string, stationNumber: number, start: number, values: Array<boolean>, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            let ackFrameBuf = ModbusRTURequest.WriteMultipleCoils(stationNumber, start, values, values.length);
            let cmdPacket = ackFrameBuf;
            let transactions_id = `WriteCoils_${host}`;
            this._transactions[transactions_id] = {
                  host: host,
                  start: start,
                  values: values,
                  quantity: values.length,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToTcpClient.call(this, `WriteCoils`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }
      /**
       * 写定时单线圈（控制值电磁阀设备）
       * @param host 地址
       * @param stationNumber 站号 
       * @param address DO地址
       * @param openTimeSec 开启时长
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      tcpComCtrl.WriteTimerCoil(host, 1, 0, 30, 2000, 3, true, function (err: string | null, data: WriteTimerCoilInfo) {
            console.log(err, data)
      })
       */
      WriteTimerCoil(host: string, stationNumber: number, address: number, openTimeSec: number, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            //6 1E240100 B000100 01 100000000102001E265
            let ackFrameBuf = ModbusRTURequest.WriteSingleRegister(stationNumber, address, openTimeSec);
            let cmdPacket = ackFrameBuf;
            let transactions_id = `WriteTimerCoil_${host}_${address}`;
            console.log(transactions_id)
            this._transactions[transactions_id] = {
                  host: host,
                  address: address,
                  value: openTimeSec,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToTcpClient.call(this, `WriteTimerCoil`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }
      /**
       * 写定时线圈（控制卷膜拉幕设备）
       * @param host 地址
       * @param stationNumber 站号 
       * @param adressArr 控制的DO地址
       * @param openTimeSecArr 控制的DO开启时长
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      tcpComCtrl.WriteTimerCoils(host, 1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10], [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110], 6000, 3, true, function (err: string | null, data: WriteTimerCoilsInfo) {
            console.log(err, data)
      })
       */
      WriteTimerCoils(host: string, stationNumber: number, adressArr: Array<number>, openTimeSecArr: Array<number>, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            console.log(host, stationNumber, adressArr, openTimeSecArr, retryInterval, retryNumber, isBackOff)
            const payload = Buffer.alloc(adressArr.length * 4)
            for (let i = 0; i < adressArr.length; i++) {
                  payload.writeUInt16BE(adressArr[i], i * 4); // 
                  payload.writeUInt16BE(openTimeSecArr[i], (i * 4) + 2); // start address
            }
            // console.log(payload)
            //01 10 0000 0004 08 0008 0033 0009 0000 EBBC
            let ackFrameBuf = ModbusRTURequest.WriteMultipleRegisters(stationNumber, 0, payload);//寄存器0为定时DO控制寄存器，回执起始地址为控制起始地址，数量为控制DO数量
            let cmdPacket = ackFrameBuf;
            let transactions_id = `WriteTimerCoils_${host}_${adressArr[0]}_${adressArr.length}`;
            console.log(transactions_id)
            this._transactions[transactions_id] = {
                  host: host,
                  start: 0,
                  timerCoils: 2,
                  quantity: adressArr.length * 2,
                  adressArr: adressArr,
                  openTimeSecArr: openTimeSecArr,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            // console.log(cmdPacket)

            _writeCmdToTcpClient.call(this, `WriteTimerCoils`, cmdPacket.toString('hex').toUpperCase(), transactions_id, Math.floor(retryInterval + Math.random() * 1000), retryNumber, isBackOff)
      }
      /**
       * 延时启动置线圈
       * @param host 地址
       * @param stationNumber 站号 
       * @param coilsStatusArr 线圈状态
       * @param delay 延时时间（ms）
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      tcpComCtrl.WriteDelayCoils(host, 1, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 10, 6000, 3, true, function (err: string | null, data: object) {
            console.log(err, data)
      })
       */
      WriteDelayCoils(host: string, stationNumber: number, coilsStatusArr: Array<number>, delay: number, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            //01 10 00FF 0002 04 FC00 07D0 8F57
            const len = coilsStatusArr.length > 16 ? 16 : coilsStatusArr.length;
            let coilsStr = "";
            for (let i = 0; i < len; i++) {
                  coilsStr = `${coilsStr}${coilsStatusArr[i]}`
            }
            let coilsValue = parseInt(coilsStr.padEnd(16, "0"), 2);
            console.log(coilsStr.padEnd(16, "0"), coilsValue)
            const payload = Buffer.alloc(4)
            payload.writeUInt16BE(coilsValue, 0); // 
            payload.writeUInt16BE(delay, 2); // 
            //01 10 0001 0004 08 0FFF 000A 000000009CF7
            console.log(coilsValue)
            let ackFrameBuf = ModbusRTURequest.WriteMultipleRegisters(stationNumber, 255, payload);//协议定义寄存器 255为延迟启动寄存器地址
            console.log(ackFrameBuf)
            let cmdPacket = ackFrameBuf;
            let transactions_id = `WriteDelayCoils_${host}`;
            this._transactions[transactions_id] = {
                  host: host,
                  start: coilsValue,
                  coilsStatusArr: coilsStatusArr,
                  quantity: delay,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };

            _writeCmdToTcpClient.call(this, `WriteDelayCoils`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)

      }

}

export {
      TcpComCtrlClass
}